#include "src/sha_helper.h"
#include <stdio.h>
#include <string.h>
#include "src/sha256.h"
#include "src/sha1.h"

namespace helper {

#define INTERNAL_FORMAT "%02x"

static std::string HashResultToReadableString(unsigned char *input, size_t len) {
    std::string result;
    result.reserve(2 * len);

    for (size_t i = 0; i < len; i++) {
        char temp[8] = {0};
        auto len = snprintf(temp, sizeof(temp), INTERNAL_FORMAT, input[i]);
        result.append(std::string(temp, len));
    }
    return result;
}

class SHA256Impl {
public:
    SHA256Impl() {
        sha256_init(&_ctx);
    }
    ~SHA256Impl() {}
    SHA256Impl &Write(const unsigned char *data, size_t len) {
        sha256_process(&_ctx, data, len);
        return *this;
    }
    void Finalize(unsigned char hash[SHA256::OUTPUT_SIZE]) {
        sha256_done(&_ctx, hash);
    }
    SHA256Impl &Reset() {
        sha256_init(&_ctx);
        return *this;
    }

private:
    sha256_context _ctx;
};

// --------------------------------------------------------------

SHA256::SHA256()
    : _impl(new SHA256Impl) {}

SHA256::~SHA256() {
    delete _impl;
}

SHA256 &SHA256::Write(const unsigned char *data, size_t len) {
    _impl->Write(data, len);
    return *this;
}

void SHA256::Finalize(unsigned char hash[OUTPUT_SIZE]) {
    _impl->Finalize(hash);
}

std::string SHA256::ReadableResult() {
    unsigned char hash[OUTPUT_SIZE] = {0};
    _impl->Finalize(hash);

    return HashResultToReadableString(hash, OUTPUT_SIZE);
}

SHA256 &SHA256::Reset() {
    _impl->Reset();
    return *this;
}

SHA1::SHA1(/* args */) {
    _impl = new ::SHA1();
}

///////////////////////////////////////////////////////////////////////////

SHA1::~SHA1() {
    delete _impl;
}

SHA1 &SHA1::Write(const unsigned char *data, size_t len) {
    _impl->Input(data, len);
    return *this;
}

bool SHA1::Finalize(unsigned char hash[OUTPUT_SIZE]) {
    unsigned int *result = reinterpret_cast<unsigned int *>(hash);
    constexpr size_t elements_count = 5;

    if (!_impl->Result(result)) {
        return false;
    }

    if (IsLittleEndian()) {
        for (size_t i = 0; i < elements_count; i++) {
            result[i] = IntegralChangeByteOrder(result[i]);
        }
    }
    return true;
}

std::string SHA1::ReadableResult() {
    unsigned char result[OUTPUT_SIZE] = {0};
    this->Finalize(result);
    return HashResultToReadableString(result, OUTPUT_SIZE);
}

SHA1 &SHA1::Reset() {
    _impl->Reset();
    return *this;
}
}  // namespace helper
